<?php
/**
 * WSDL 接口连接类
 * pipeId最好保存到内存
 * 调用方式 :
 *  通用接口 :net::inst($instName)->execute($cmd_num,$param);
 *  封装接口 :(不分区分大小写)
 *      get前缀后期考虑加入缓存
 *       net::inst('bizFG')->getShow($param);
 *      直接访问，无缓存
 *       net::inst('bizFG')->Show();
 */
class net
{
        /*状态标识*/
        const NET_SUCCEED  = 10;
        const NET_USER_LOGOUT = 19;
        const NET_EMPTY = 21;
        const NET_NOT_EXIST = 22;
        const NET_SERVICE_ERROR = 30;
        const NET_EXIST = 31;
        

        private static $_instance = array();
        private $config;  //配置信息
        private $cmds;    //命令集

         /** 构建，初始化 链接客户端
         * @method __construct
         * @param  [type]      $config [description]
         * @access private
         * @author NewFuture
         */
        protected function __construct($instName)
        {
                date_default_timezone_set('PRC');
                $conf = parse_ini_file(APP_PATH.'/conf/'.$instName.".ini",true);
                $this->config = $conf['net'];
                $this->cmds = $conf['biz'];
        }

        /**
         * 获取实例化对象
         * @method getInstance
         * @return [type]      [description]
         * @author NewFuture
         */
        public static function inst($instName)
        {
                if (!isset(self::$_instance[$instName]))
                {
                        self::$_instance[$instName] = new self($instName);
                }
                return self::$_instance[$instName];
        }

        /**
         * 魔法函数，直接执行命令
         * GET开头的函数会调用缓存
         * @method __callStatic
         * @param  [type]  $name [description]
         * @param  [type]  $data [description]
         * @return [type]        [description]
         * @author NewFuture
         */
        public function __call($name, $argument)
        {       
                $name  = strtoupper($name);
                $param = empty($argument) ? array() : $argument[0];

                /*过滤GET*/
                if (strpos($name, 'GET') === 0)
                {
                                                       //正确姿势应该尝试缓存
                        $name      = substr($name, 3); //去掉get
                        $cache_key = 'net_' . $name . implode('_', $param);
                        if ($data = kv::get($cache_key))
                        {
                                return $data;
                        }
                }

                // var_dump($name); //调试用
                $cmds = $this->cmds;
                if (isset($cmds[$name]))
                {
                        /*命令存在于命令集中，执行相应命令*/
                        $data = self::execute($cmds[$name], $param);
                        if ($data && isset($cache_key))
                        {
                                kv::set($cache_key, $data, $this->config['cachetime']); //缓存
                        }
                        return $data;
                }
                else
                {
                        return null;
                }
        }

        /**
         * 发送请求数据,
         * @method request
         * @param  [type]  $pipeId  [通道id]
         * @param  [type]  $cmd_num [命令字]
         * @param  array   $data    [数据]
         * @return [type]           [响应对象]
         * @author NewFuture
         */
        private function request($cmd_num, $data = array())
        {
                //创建返回一个套接字，也称通讯节点
                $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
                //开启一个套接字连接
                $con=socket_connect($socket,$this->config['host'],intval($this->config['port']));
                if(!$con){
                        socket_close($socket);//关闭socket资源
                        return self::NET_SERVICE_ERROR;
                }
                //获取socket选项
                socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec"=>120, "usec"=>0));
    

                $rdata = array_reverse($data);//反向输出数组
                $rdata['cmd'] = $cmd_num;
                $sdata = array_reverse($rdata);//在反向输出数组，使cmd位于第一个元素
                $json = json_encode($sdata);
                $packend = pack('c', '0');//用于将其它进制的数字压缩到位字符串之中。也就是把其它进制数字转化为ASCII码字符串
                $req = $json.$packend;
                socket_write($socket,$req);
        
                $answ = '';
                while (true) {
                        $tmp = socket_read($socket,1024);
                        if ($tmp != null) {
                                $answ .= $tmp;
                                for ($i = 0; $i < strlen($tmp); $i++) {
                                        if (substr($tmp, $i, 1) == $packend) {
                                                $answ = substr($answ, 0, strlen($answ) - 1);
                                                socket_shutdown($socket);
                                                socket_close($socket);  
                                                $Response = json_decode($answ);
                                                return $Response;
                                        }
                                }
                        }
                        else {
                                socket_shutdown($socket);//关闭读
                                socket_close($socket);  
                                return null;
                        }
                }
        }

        /**
         * 执行请求数据
         * @method execute
         * @param  [int]  $cmd_num [命令号]
         * @param  [数组]  $data    [数据]
         * @return [type]           [数据对象]
         * @author NewFuture
         */
        private function execute($cmd_num, $data = array())
        {
            $Response = $this->request($cmd_num, $data);
            if ($Response == null || self::NET_SERVICE_ERROR == $Response->ret)
            {
                    $Response = $this->request($cmd_num, $data);
            } 
            return $Response;
        }

}
